﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Duellum.Core.Resources;

namespace Duellum.Core
{
	public static class DuelPluginLoader
	{
		static public string[] PluginSources = new[] {".", ".\\bin"};
		
		static public IEnumerable<DuelPluginName> FindPlugins()
		{
			return
				PluginSources
				.Select((Func<string,string>)DuelResources.MakeAbsolutePath)
				.SelectMany(path => LoadAssembliesFromFolder(path))
				.FindPluginsFromAssemblies()
				.ToArray();
		}

		/*
		[Obsolete]
		static public IEnumerable<DuelPluginName> FindPluginsFromCurrentDomainAssemblies()
		{
			IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies();
			
			assemblies = assemblies.Where(
				asm => !asm.IsDefined(typeof(AssemblyCompanyAttribute), false)
					|| asm.GetSingleAttr<AssemblyCompanyAttribute>(false).Company != "Microsoft Corporation"
			);
			
			return FindPluginsFromAssemblies(assemblies).ToList();
		}

		[Obsolete]
		static public IDictionary<DuelTypeId,DuelType> GatherTypesFromPlugins(this IEnumerable<DuelPluginProxy> plugins)
		{
			IDictionary<DuelTypeId,DuelType> list = new Dictionary<DuelTypeId,DuelType>();

			var types = (
			    from p in plugins
			    from e in p.GetBaseTypes()
			    select e
			).ToList();
			types.ForEach(t => list.Add(t.Id, t));
			
			var extensions = (
			    from p in plugins
			    from e in p.GetExtendedTypes()
			    select e
			).Union(
			    from p in plugins
			    from e in p.GetMetadata()
			    select e
			).ToList();
			extensions.ForEach(e => list[e.Id].MergeWith(e));
			
			return list;
		}//*/

		static internal IEnumerable<DuelPluginName> FindPluginsFromAssemblies(this IEnumerable<Assembly> asms)
		{
			return asms.SelectMany(asm => FindPluginsFromAssembly(asm));
		}
		
		static internal IEnumerable<DuelPluginName> FindPluginsFromAssembly(Assembly asm)
		{
			//Type iPluginType = typeof(IDuelPlugin);
			//Type attrType = typeof(DuelPluginAttribute);

			//return (
			//    from type in asm.GetTypes()
			//    where type.IsClass &&
			//        iPluginType.IsAssignableFrom(type) &&
			//        !type.IsAbstract &&
			//        type.IsDefined(attrType, true)
			//    select new DuelPluginProxy(
			//        (DuelPluginAttribute)type.GetCustomAttributes(attrType, false).Single(),
			//        (IDuelPlugin)Activator.CreateInstance(type),
			//        asm
			//    )
			//);
			return (
			    asm.GetTypes()
			    .Select(type => DuelPluginName.FromType(type))
			    .Where(name => name != null)
			    //.Select(name => name.CreateProxy())
			);
		}
		
		static internal IEnumerable<Assembly> LoadAssembliesFromFolder(string path)
		{
			if (!Directory.Exists(path)) {
				return Enumerable.Empty<Assembly>();
			} else {
				var asmFiles = Directory.GetFiles(path, "*.dll")
						.Union(Directory.GetFiles(path, "*.exe"));
				
				return asmFiles.Select(asmFile => LoadAssemblyOrNull(asmFile)).Where(asm => asm != null);
			}
		}
		
		static internal Assembly LoadAssembly(string path)
		{
			return Assembly.LoadFrom(path);
			//return Assembly.ReflectionOnlyLoadFrom(path);
		}

		static internal Assembly LoadAssemblyOrNull(string path)
		{
			Assembly asm;
			return TryLoadAssembly(path, out asm) ? asm : null;
		}
		
		static internal bool TryLoadAssembly(string path, out Assembly assembly)
		{
			assembly = null;
			try {
				if (!File.Exists(path)) return false;
				assembly = LoadAssembly(path);
				return true;
			} catch (BadImageFormatException) {
				return false;
			} catch (IOException) {
				return false;
			}
		}
	}
}
